package com.hoki.zj.hrm.service.impl;

import com.alibaba.fastjson.JSON;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.hoki.zj.hrm.asserts.GlobalAssert;
import com.hoki.zj.hrm.domain.KillCourse;
import com.hoki.zj.hrm.domain.KillOrder;
import com.hoki.zj.hrm.domain.PayFlow;
import com.hoki.zj.hrm.dto.AliPayNotifyDto;
import com.hoki.zj.hrm.exception.GlobalException;
import com.hoki.zj.hrm.prop.AliPayProperties;
import com.hoki.zj.hrm.service.IPayFlowService;
import com.hoki.zj.hrm.service.IPayService;
import com.hoki.zj.hrm.to.PayResultTo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

import static com.hoki.zj.hrm.constant.MQMessageConst.*;
import static com.hoki.zj.hrm.domain.PayFlow.*;

@Service
@Slf4j
public class PayServiceImpl implements IPayService {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private AliPayProperties properties;

    @Autowired
    private IPayFlowService payFlowService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 1.发起支付申请
     */
    @Override
    public String payApply(String orderNo) {
        // 1.判断订单号合法性
        GlobalAssert.hasText(orderNo, "订单号无效!");
        // 2.查询redis中的订单(是否已经下单成功)
        KillOrder killOrder = (KillOrder) redisTemplate.opsForValue().get("order:" + orderNo);
        GlobalAssert.isNull(killOrder, "订单未创建,请稍后");
        // 3.判断订单是否已经超时
        Date now = new Date();
        if (now.getTime() - killOrder.getCreateTime().getTime() > 60000) { // 延迟队列处理过期订单的消息时60s
            // 3.1 删除Redis中的订单,避免超时支付
            redisTemplate.delete("order:" + killOrder.getOrderNo());
            throw new RuntimeException("支付超时,交易已关闭!");
        }
        // 4.获取redis中的秒杀记录,获取课程
        KillCourse courseInRedis = (KillCourse)redisTemplate.opsForValue().get("KILL_LOG:" + killOrder.getUserId());
        // 5. 设置支付参数
        Factory.setOptions(properties.getOptions());
        try {
            // 5.1 支付名
            assert courseInRedis != null;
            String subject = courseInRedis.getCourseName();
            // 5.2 支付单号
            String payNo = UUID.randomUUID().toString();
            // 5.3 支付金额
            BigDecimal amount = killOrder.getTotalPrice().setScale(2, BigDecimal.ROUND_HALF_UP);
            subject = "购买课程: "+subject;
            AlipayTradePagePayResponse response = Factory.Payment.Page()
                    .pay(subject, payNo,
                            amount.toString()
                            , properties.getReturnUrl());

            // 3. 处理响应或异常
            log.warn("支付结果：-> {}", response.body);
            if (ResponseChecker.success(response)) {
                log.info("调用成功");
            } else {
                log.error("调用失败!");
            }

            //保存支付订单
            boolean saveResult = savePayFlow(orderNo, courseInRedis, subject, payNo, amount);
            GlobalAssert.isSuccess(saveResult, "保存失败!");
            //删除Redis中的订单
            redisTemplate.delete("order:" + orderNo);
            //删除秒杀记录
//            redisTemplate.delete("KILL_LOG:" + killOrder.getUserId());
            return response.body;
        } catch (Exception e) {
            log.error("调用异常,原因 -> {}", e.getMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    //保存支付订单
    private boolean savePayFlow(String orderNo,
                                KillCourse courseInRedis,
                                String subject, String payNo,
                                BigDecimal amount) {
        PayFlow payFlow = new PayFlow();
        payFlow.setAmount(amount);
        //业务类型秒杀支付
        payFlow.setBusinessType(1);
        payFlow.setCreateTime(new Date());
        payFlow.setIntro(subject);
        payFlow.setOrderNo(orderNo);
        payFlow.setPayNo(payNo);
        payFlow.setPayStatus(PAY_TYPE_ALI_PAY);
        //关联一个业务对象的ID
        payFlow.setRelationId( courseInRedis.getId());

        return payFlowService.insert(payFlow);
    }

    /**
     * 支付宝异步回调处理
     * @param dto
     * @return
     * @throws Exception
     */
    @Override
    public String notifyHandler(AliPayNotifyDto dto) throws Exception {
        //1.判断参数
        //2.验证签名
        Map<String, String> paramsMap = JSON.parseObject(JSON.toJSONString(dto),Map.class);
        boolean signVerified = Factory.Payment.Common().verifyNotify(paramsMap);

        GlobalAssert.isSuccess(signVerified , "验签失败");

        //3.根据单号查询支付单号
        String out_trade_no = dto.getOut_trade_no();

        //锁，幂等判断
        RLock lock = redissonClient.getLock("notifylock:" + out_trade_no);
        lock.lock();
        try {
            PayFlow payFlow = payFlowService.selectByPayNo(out_trade_no);
            GlobalAssert.isNull(payFlow,"无效的交易单号");

            if(payFlow.getPayStatus() != PAY_STATUS_PAYING){
                return "success";
            }

            if(0 != payFlow.getAmount().compareTo(new BigDecimal(dto.getTotal_amount()))){
                throw new GlobalException("订单金额不匹配!");
            }

            //4.修改支付单状态
            if(dto.isTradeWit()){
                return "fail";
            }

            if(dto.isTradeSuccess()){
                payFlow.setPayStatus(PAY_STATUS_SUCCESS);
            }else if(dto.isTradeExpireOrFail()){
                payFlow.setPayStatus(PAY_STATUS_FAILURE);
            }
            payFlowService.updateById(payFlow);

            //5.发送支付结果消息到MQ
            rabbitTemplate.convertAndSend(
                    EXCHANGE_NAME_PAY_RESULT,
                    "result.pay",
                    new PayResultTo(payFlow.getOrderNo() ,
                            payFlow.getPayStatus() == PAY_STATUS_SUCCESS ));
        }finally {
            if(lock.isLocked()){
                lock.unlock();
            }
        }
        return null;
    }
}
